package hr.fer.pus.dll_will.sherlock;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.LinkedList;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * Implements standard security utilities.
 * 
 * 
 * @author Zoki
 */
public class Security {

	/**
	 * Generates private and public 1024 bit RSA keys.
	 * @return KeyPair, a public and private key pair
	 */
	public static KeyPair generateKeys(){
		try {
			KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
			gen.initialize(1024);
			return gen.generateKeyPair();
		} catch (NoSuchAlgorithmException ignorable) {
		}
		return null;
	}
	
	/**
	 * Encrypts plain text using RSA algorithm.
	 * @param plain plain text to encrypt
	 * @param exp public key exponent
	 * @param mod public key modulus
	 * @return cyphertext, RSA encrypted plain text
	 */
	public static String encrypt(String plain, String key){
		  String[] split = key.split(" ");
		  String exp = split[1];
		  String mod = split[0];
		  byte[] clearText = plain.getBytes();
		  RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(mod),
				  new BigInteger(exp));
		  try {   
			   Cipher rsa = Cipher.getInstance("RSA");
			   KeyFactory fac = KeyFactory.getInstance("RSA");
			   RSAPublicKey pKey = (RSAPublicKey) fac.generatePublic(keySpec);
			   rsa.init(Cipher.ENCRYPT_MODE, pKey);
			   int maxSize = 117; // 128 - 11 padding
			   byte[] buff = new byte[maxSize];
			   int blocks = (int) Math.ceil((double)clearText.length/maxSize);
			   byte[] encrypted = new byte[blocks*128];
			   int i = 0;
			   while(i < encrypted.length){
				   buff = rsa.doFinal(clearText, i, Math.min(maxSize, 
						   clearText.length - i));
				   for(byte b : buff){
					   encrypted[i++] = b;
				   }
				   buff = new byte[maxSize];
			   }
			   return new String(Base64Coder.encode(encrypted));
		} catch (NoSuchAlgorithmException e) {
			System.err.println(e.getMessage());
		} catch (NoSuchPaddingException e) {
			System.err.println(e.getMessage());
		} catch (InvalidKeyException e) {
			System.err.println(e.getMessage());
		} catch (IllegalBlockSizeException e) {
			System.err.println(e.getMessage());
		} catch (BadPaddingException e) {
			System.err.println(e.getMessage());
		} catch (InvalidKeySpecException e) {
			System.err.println(e.getMessage());
		}
		return null;
	}
	
	/**
	 * Decrypts a give cyphertext encrypted with RSA algorithm.
	 * @param cypher cyphertext to decrypt
	 * @param exp private key exponent
	 * @param mod private key modulus
	 * @return plain deciphered text
	 */
	public static String decrypt(String cypher, String key){
		String[] split = key.split(" ");
		String exp = split[1];
		String mod = split[0];
		RSAPrivateKeySpec spec = new RSAPrivateKeySpec(new BigInteger(mod), 
				new BigInteger(exp));
		try {
			RSAPrivateKey rpk = (RSAPrivateKey) KeyFactory.getInstance("RSA")
									.generatePrivate(spec);
			Cipher rsa = Cipher.getInstance("RSA");
			rsa.init(Cipher.DECRYPT_MODE, rpk);
			byte[] data = Base64Coder.decode(cypher);
			
			int maxBlockSize = 128;
			List<byte[]> resBuffer = new LinkedList<byte[]>();
			int decryptedSize = 0;
			for (int head = 0; head < data.length; head += maxBlockSize) {
				byte[] buff = rsa.doFinal(data, head, maxBlockSize);
			   	decryptedSize += buff.length;
			   	resBuffer.add(buff);
			}
			byte[] decrypted = new byte[decryptedSize];
			int i = 0;
			for (byte[] tmp : resBuffer) {
				for (byte b : tmp)
					decrypted[i++] = b;
			}
			return new String(decrypted);
			
		} catch (NoSuchAlgorithmException e) {
			System.err.println(e.getMessage());
		} catch (InvalidKeySpecException e) {
			System.err.println(e.getMessage());
		} catch (NoSuchPaddingException e) {
			System.err.println(e.getMessage());
		} catch (InvalidKeyException e) {
			System.err.println(e.getMessage());
		} catch (IllegalBlockSizeException e) {
			System.err.println(e.getMessage());
		} catch (BadPaddingException e) {
			System.err.println(e.getMessage());
		} 
		
		return null;
	}
}
